#pragma once
#include <string>
#include <unordered_map>

#include "bio.h"
#include "picohttpparser.h"

namespace bio::web {
namespace priv {
struct ci_hash {
    size_t operator()(const std::string& val) const {
        std::string lower;
        lower.resize(val.size());
        std::transform(val.begin(), val.end(), lower.begin(), ::tolower);
        return std::hash<std::string>{}(lower);
    }
};
struct ci_pred {
    bool operator()(const std::string& a, const std::string& b) const {
        size_t as = a.size();
        size_t bs = b.size();
        if (as != bs) {
            return false;
        }
        for (size_t i = 0; i < as; i++) {
            if (::tolower(a[i]) != ::tolower(b[i])) {
                return false;
            }
        }
        return true;
    }
};
}  // namespace priv
using CIMap = std::unordered_multimap<std::string, std::string, priv::ci_hash, priv::ci_pred>;

class ParserContext {
   private:
    static constexpr const size_t PARSE_BUF_LEN = 4096;
    static constexpr const size_t PARSE_MAX_HDR = 100;

   public:
    class parse_error : public std::runtime_error {
       public:
        parse_error(const char* what) : runtime_error(what) {}
    };

    struct slice {
        const char* start;
        size_t len;
        std::string to_string() { return std::string(start, start + len); }
    };

    CIMap headers;
    bytes buf{PARSE_BUF_LEN};
    size_t leftover;
    slice method;
    slice req_path;

    ParserContext() {}
    ~ParserContext() {}

    __attribute__((no_sanitize("undefined"))) async<void> parse(TcpClient& client) {
        int pret;
        int minor_version;
        struct phr_header headers[PARSE_MAX_HDR];
        size_t num_headers = PARSE_MAX_HDR;
        size_t buflen = 0, prevbuflen = 0;

        while (true) {
            size_t rret = co_await client.read(buf, buflen, buf.size() - buflen);
            prevbuflen = buflen;
            buflen += rret;
            pret = phr_parse_request(reinterpret_cast<const char*>(buf.data()), buflen,
                                     const_cast<const char**>(&method.start), &method.len,
                                     const_cast<const char**>(&req_path.start), &req_path.len,
                                     &minor_version, headers, &num_headers, prevbuflen);
            if (pret > 0) {
                break;
            } else if (pret == -1) {
                throw parse_error{"Parse failed"};
            }
            assert(pret == -2);
            if (buflen == PARSE_BUF_LEN) {
                throw parse_error{"Request is too long"};
            }
        }

        leftover = pret;
        for (size_t i = 0; i < num_headers; ++i) {
            this->headers.emplace(
                std::piecewise_construct,
                std::forward_as_tuple(headers[i].name, headers[i].name + headers[i].name_len),
                std::forward_as_tuple(headers[i].value, headers[i].value + headers[i].value_len));
        }

        co_return;
    }
};

};  // namespace bio::web
